//
// Created by tong on 9/27/24.
//

#include "GRDECLRaw.h"

#include <functional>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>

void GRDECLParser::skipComment(std::ifstream &is)
{
    char tempChar;
    while (is.get(tempChar))
    {
        if (tempChar == '-' || tempChar == '#')
        {
            is.get(tempChar);
            if (std::isdigit(tempChar))
            {
                is.putback(tempChar);
                is.putback('-');
                return;
            }
            else
            {
                while (is.get(tempChar))
                {
                    if (tempChar == '\n' || tempChar == '\r')
                    {
                        break;
                    }
                }
            }
        }
        else if (tempChar == ' ' || tempChar == '\n' || tempChar == '\r')
        {
            continue;
        }
        else
        {
            is.putback(tempChar);
            break;
        }
    }
}

bool GRDECLParser::moveToKeyWordData(std::ifstream &is)
{
    skipComment(is);
    char tempChar;
    while (is.get(tempChar))
    {
        if (tempChar == '\'')
        {
            is.putback(tempChar);
            return true;
        }
        else if (tempChar == ' ' || tempChar == '\n' || tempChar == '\r')
        {
            continue;
        }
        else
        {
            is.putback(tempChar);
            break;
        }
    }
    while (is.get(tempChar))
    {
        if (std::isdigit(tempChar))
        {
            is.putback(tempChar);
            return true;
            break;
        }
        if (tempChar == '-')
        {
            is.get(tempChar);
            if (std::isdigit(tempChar))
            {
                is.putback(tempChar);
                is.putback('-');
                return true;
                break;
            }
        }
        else if (!std::isalnum(tempChar))
        {
            break;
        }
        skipComment(is);
    }
    return false;
}

bool GRDECLParser::moveToNextKeyWord(std::ifstream &is)
{
    skipComment(is);
    char tempChar;
    while (is.get(tempChar))
    {
        if (std::isalpha(tempChar))
        {
            is.putback(tempChar);
            return true;
        }
        skipComment(is);
    }
    return false;
}

template <>
void GRDECLParser::readData(std::ifstream &is, vector<bool> &data)
{
    std::function<bool(const std::string &)> stob =
        [](const std::string &ss)
    { return ss == "1"; };
    data.resize(0);
    std::string tempToken;
    while (is >> tempToken)
    {
        if (tempToken.find('*') != std::string::npos)
        {
            // std::cout<<tempToken;
            std::stringstream ss(tempToken);
            std::string nums;
            std::string bs;
            std::getline(ss, nums, '*');
            std::getline(ss, bs, '*');
            const int nValue = std::stoi(nums);
            bool value = stob(bs);
            for (int i = 0; i < nValue; i++)
            {
                data.push_back(value);
            }
        }
        else if (std::isdigit(tempToken[0]))
        {
            data.push_back(stob(tempToken));
        }
        else if (tempToken == "/")
        {
            break;
        }
        else
        {
            // unknow token stop
            std::cerr << "Unknow token: " << tempToken << " in positon: "
                      << is.tellg() << std::endl;
            std::abort();
            break;
        }
    }
}

template <>
void GRDECLParser::readData(std::ifstream &is, vector<double> &data)
{
    data.resize(0);
    std::string tempToken;
    while (is >> tempToken)
    {
        if (tempToken.find('*') != std::string::npos)
        {
            std::stringstream ss(tempToken);
            std::string nums;
            std::string bs;
            std::getline(ss, nums, '*');
            std::getline(ss, bs, '*');
            const int nValue = std::stoi(nums);
            double value = std::stod(bs);
            for (int i = 0; i < nValue; i++)
            {
                data.push_back(value);
            }
        }
        else if (std::isdigit(tempToken[0]) || tempToken[0] == '-')
        {
            data.push_back(std::stod(tempToken));
        }
        else if (tempToken == "/")
        {
            break;
        }
        else
        {
            // unknow token stop
            std::cerr << "Unknow token: " << tempToken << " in positon: "
                      << is.tellg() << std::endl;
            std::abort();
            break;
        }
    }
}

GRDECLRawData::GRDECLRawData(const std::string &filePath,const std::string& dataPath)
{

    std::ifstream GRDECLFileIS(filePath);
    if (!GRDECLFileIS.is_open())
    {
        std::cerr << "无法打开文件:" << filePath << std::endl;
        std::abort();
    }
    coord.resize(0);
    zcoord.resize(0);
    actnum.resize(0);

    // parse grdecl

    using namespace GRDECLParser;
    while (moveToNextKeyWord(GRDECLFileIS))
    {
        std::string tempToken;
        GRDECLFileIS >> tempToken;
        if (tempToken == GRDECLKeyWords::SPECGRID &&
            moveToKeyWordData(GRDECLFileIS))
        {
            while (GRDECLFileIS >> tempToken)
            {
                if (tempToken == "/")
                {
                    break;
                }
                dimI = std::stoi(tempToken);
                GRDECLFileIS >> tempToken;
                dimJ = std::stoi(tempToken);
                GRDECLFileIS >> tempToken;
                dimK = std::stoi(tempToken);
                // read 1 - unused data
                GRDECLFileIS >> tempToken;
                // read F - unused data
                GRDECLFileIS >> tempToken;
            }
        }
        else if (tempToken == GRDECLKeyWords::COORD &&
                 moveToKeyWordData(GRDECLFileIS))
        {
            readData(GRDECLFileIS, coord);
        }
        else if (tempToken == GRDECLKeyWords::ZCORN &&
                 moveToKeyWordData(GRDECLFileIS))
        {
            readData(GRDECLFileIS, zcoord);
        }
        else if (tempToken == GRDECLKeyWords::ACTNUM &&
                 moveToKeyWordData(GRDECLFileIS))
        {
            readData(GRDECLFileIS, actnum);
        }
    }
    if(!dataPath.empty())
    {
        std::ifstream DataFileIS(dataPath);
        if (!DataFileIS.is_open())
        {
            std::cerr << "无法打开文件:" << dataPath << std::endl;
            std::abort();
        }
        while (moveToNextKeyWord(DataFileIS))
        {
            std::string tempToken;
            DataFileIS >> tempToken;
            if (tempToken == GRDECLKeyWords::PIOSSON &&
                    moveToKeyWordData(DataFileIS))
            {
                readData(DataFileIS, Poisson_);
            }
            else if (tempToken == GRDECLKeyWords::YOUNGMOD &&
                    moveToKeyWordData(DataFileIS))
            {
                readData(DataFileIS, YoungsMod_);
            }
            else if (tempToken == GRDECLKeyWords::PORO &&
                    moveToKeyWordData(DataFileIS))
            {
                readData(DataFileIS, Porosity_);
            }
            else if (tempToken == GRDECLKeyWords::ROCKDEN &&
                    moveToKeyWordData(DataFileIS))
            {
                readData(DataFileIS, RockDensity_);
            }
        }
        std::cout << "finish reading data file: " << dataPath << std::endl;
        if (Poisson_.size() != YoungsMod_.size() &&
            Poisson_.size() != actnum.size() &&
            Poisson_.size() != Porosity_.size() &&
            Poisson_.size() != RockDensity_.size())
        {
            std::cerr << "Poisson size not match with YoungsMod size!" << std::endl;
            std::abort();
        }
    }
    if (coord.size() == 0 && zcoord.size() == 0 && actnum.size() == 0)
    {
        std::cerr << "输入文件：" << filePath << " 无网格数据。" << std::endl;
        std::abort();
    }

    std::cout << "GRDECL Data:: I=" << dimI << " J=" << dimJ << " K=" << dimK
              << "\n coord.size()==" << coord.size()
              << " zoord.size()==" << zcoord.size()
              << " actnum.size()==" << actnum.size() << std::endl;
}

PointFieldMesh::PointFieldMesh(const std::string &filePath)
{

    std::ifstream pointFieldFileIS(filePath);
    if (!pointFieldFileIS.is_open())
    {
        std::cerr << "无法打开文件:" << filePath << std::endl;
        std::abort();
    }
    points_.resize(0);
    data_.resize(0);
    // parse grdecl
    std::string tempToken;
    pointFieldFileIS >> tempToken;
    if (tempToken == "x")
    {
        // inpute file
        pointFieldFileIS >> tempToken; // Get y
        pointFieldFileIS >> tempToken; // Get z
        pointFieldFileIS >> tempToken; // Get S1
        while (pointFieldFileIS >> tempToken)
        {
            Point tempPoint;
            tempPoint.x = std::stod(tempToken);
            pointFieldFileIS >> tempPoint.y;
            pointFieldFileIS >> tempPoint.z;
            points_.push_back(tempPoint);
            double dataValue;
            pointFieldFileIS >> dataValue;
            data_.push_back(dataValue);
        }
    }
    else if (tempToken == "No.")
    {
        pointFieldFileIS >> tempToken; // Get x
        pointFieldFileIS >> tempToken; // Get y
        pointFieldFileIS >> tempToken; // Get z
        while (pointFieldFileIS >> tempToken)
        {
            // pointFieldFileIS >> tempToken; // skip number
            Point tempPoint;
            // tempPoint.x = std::stod(tempToken);
            pointFieldFileIS >> tempPoint.x;
            pointFieldFileIS >> tempPoint.y;
            pointFieldFileIS >> tempPoint.z;
            points_.push_back(tempPoint);
        }
    }
    else
    {
        std::cerr << "未匹配输入文件首字符串" << tempToken
                  << " : " << filePath << std::endl;
        std::abort();
    }

    if (points_.size() == 0 && data_.size() == 0)
    {
        std::cerr << "输入文件：" << filePath << " 无数据。" << std::endl;
        std::abort();
    }

    std::cout << "PointField Data:: pointsNums=" << points_.size() << ", DataSize=" << data_.size() << std::endl;

    calcNodeDim();
}

void PointFieldMesh::vtkCellNodeDataWriter(const std::string &outputPath, const DoubleList &data)
{
    if (data.size() != points_.size())
    {
        std::cerr << "Data size not match with points size!" << std::endl;
        std::abort();
    }

    std::ofstream outputFile(outputPath);

    if (!outputFile)
    {
        std::cerr << " Can not open: " << outputPath << std::endl;
        std::abort();
    }

    /**********Write header***********/
    outputFile << "# vtk DataFile Version 2.0\n";
    outputFile << "Point Mesh\n";
    outputFile << "ASCII\n";
    outputFile << "DATASET UNSTRUCTURED_GRID\n";
    outputFile << "POINTS " << points_.size() << " double"
               << "\n";
    /**********Write Points Data******************/
    for (size_t i = 0; i < points_.size(); i++)
    {
        outputFile << points_[i].x << "  ";
        outputFile << points_[i].y << "  ";
        outputFile << points_[i].z << "\n";
    }
    outputFile << "\n";
    /**********Write CEll Data************/
    // CELL header
    outputFile << "CELLS " << nCell << " " << nCell * 9 << "\n";
    for (int z = 0; z < nZ - 1; z++)
    {
        for (int y = 0; y < nY - 1; y++)
        {
            for (int x = 0; x < nX - 1; x++)
            {
                outputFile << "8  ";
                outputFile << z * nX * nY + x * nY + y << " ";                 // 0
                outputFile << z * nX * nY + x * nY + y + 1 << " ";             // 1
                outputFile << z * nX * nY + (x + 1) * nY + y + 1 << " ";       // 2
                outputFile << z * nX * nY + (x + 1) * nY + y << " ";           // 3
                outputFile << (z + 1) * nX * nY + x * nY + y << " ";           // 4
                outputFile << (z + 1) * nX * nY + x * nY + y + 1 << " ";       // 5
                outputFile << (z + 1) * nX * nY + (x + 1) * nY + y + 1 << " "; // 6
                outputFile << (z + 1) * nX * nY + (x + 1) * nY + y << "\n";    // 7
            }
        }
    }
    // "Writing cell types" << endl;
    outputFile << "CELL_TYPES " << nCell << '\n';
    for (int i = 0; i < nCell; i++)
    {
        outputFile << "12\n";
    }
    outputFile << '\n';

    /**********Write Data************/
    outputFile << "POINT_DATA " << points_.size() << "\n";
    outputFile << "SCALARS data double 1\n";
    outputFile << "LOOKUP_TABLE default\n";
    for (size_t ip = 0; ip < points_.size(); ip++)
    {
        outputFile << data[ip] << "\n";
    }

    std::cout << "Finish writing: " << outputPath << std::endl;
}

void PointFieldMesh::writeRequiredData(const std::string& outputPath)
{
    std::ofstream outputFile(outputPath);

    if (!outputFile)
    {
        std::cerr << " Can not open: " << outputPath << std::endl;
        std::abort();
    }
    /**********Write header***********/
    //outputFile << "# vtk DataFile Version 2.0\n";
    outputFile<< std::setprecision(12);
    for (size_t i = 0; i < data_.size(); i++)
    {
        outputFile << data_[i] << "\n";
    }
}


void PointFieldMesh::calcNodeDim()
{
    nY = -1;
    int currentXY = 0;
    int currentY = 0;
    for (size_t i = 1; i < points_.size(); i++)
    {
        currentXY++;
        currentY++;
        if ((points_[i].z == points_[i - 1].z) &&
            (points_[i - 1].x < points_[i].x) &&
            (points_[i - 1].y > points_[i].y))
        {
            if (nY == -1)
            {
                nY = currentY;
            }
            if ((nY != -1) && (currentY != nY))
            {
                std::cerr << "Y direction grid size not match!" << std::endl;
                std::abort();
            }
            currentY = 0;
        }

        if (points_[i].z != points_[i - 1].z)
        {
            break;
        }
    }

    if (currentXY % nY)
    {
        std::cerr << "X direction grid size not match!" << std::endl;
        std::abort();
    }

    nX = currentXY / nY;

    if (points_.size() % (nX * nY))
    {
        std::cerr << "X*Y grid size not match!" << std::endl;
        std::abort();
    }
    nZ = int(points_.size()) / (nX * nY);
    nCell = (nX - 1) * (nY - 1) * (nZ - 1);
}

void GRDECLRawData::freeData()
{
    coord.resize(0);
    zcoord.resize(0);
    actnum.resize(0);
    Poisson_.resize(0);
    YoungsMod_.resize(0);
}